from functools import partial


class FSM(object):
    """A very simple class for finite state machines."""
    _enter_prefix      = "enter_"
    _exit_prefix       = "exit_"
    _transition_prefix = "transition_"
    initial_state      = "init"
    
    def __init__(self):
        self.current_state   = None  # current state
        self.transient_state = None  # transient state (used during transitions)
        self.previous_state  = None  # previous state
        self.symbol          = None  # entered symbol
        self.driver          = FSMDriver(self)
        
    @property
    def state(self):
        return self.transient_state if self.current_state is None else self.current_state
        
    def init(self):
        """Enter the machine's initial state."""
        self.enter(self.initial_state)
        
    def input(self, symbol, *args, **kwargs):
        """Feed a symbol to the state machine. The symbol is processed by the transition function 
        defined by the current state (or the default transition function if the current state does
        not have a transition function). The transition function should return the name of the 
        state the machine should jump to."""
        if self.state is None:
            raise ValueError("machine state is undefined")
        transition_fnc = self.__get_function(self._transition_prefix, self.state)
        if transition_fnc is None:
            raise ValueError("unable to find transition function")
        self.symbol = symbol
        self.enter(transition_fnc(symbol), *args, **kwargs)
        self.symbol = None
        
    def enter(self, new_state, *args, **kwargs):
        """Enter a given state in the machine. Before calling the enter function of the new state,
        the exit function of the old state is executed."""
        # exit previous state
        print "%s: %s -> %s" % (self, self.state, new_state)
        if self.state is not None:
            exit_fnc = self.__get_function(self._exit_prefix, self.state)
            if exit_fnc is not None:
                exit_fnc()
        # enter new state
        self.previous_state  = self.state
        self.current_state   = None
        self.transient_state = new_state
        enter_fnc = self.__get_function(self._enter_prefix, new_state)
        if enter_fnc is not None:
            enter_fnc(*args, **kwargs)
        if self.transient_state is not None:
            self.current_state   = self.transient_state
            self.transient_state = None
            
    def __get_function(self, prefix, state):
        try:
            return getattr(self, prefix + state)
        except AttributeError:
            pass
        try:
            return getattr(self, prefix + "default")
        except AttributeError:
            return None
            
            
class FSMDriver(object):
    def __init__(self, machine):
        object.__setattr__(self, "machine", machine)
        
    def __getattr__(self, symbol):
        return partial(object.__getattribute__(self, "machine").input, symbol)
        